package com.atguigu.gmall.mq.reveiver;

import com.rabbitmq.client.Channel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;


/**
 * @author: atguigu
 * @create: 2023-05-08 14:49
 */
@Slf4j
@Component
public class ConfirmReceiver {

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 业务操作天然支持幂等:查询,删除,修改(固定修改为A->B)
     *
     * @param msg
     * @param channel
     * @param message
     */
    @SneakyThrows
    @RabbitListener(
            bindings = @QueueBinding(
                    exchange = @Exchange("exchange.confirm"),
                    value = @Queue("confirm.queue"),
                    key = "routing.confirm"
            )
    )
    public void receiverConfirmMessage(String msg, Channel channel, Message message) {
        //如果开启手动确认, 参数一:投递消息消息标签  参数二:是否批量确认
        //消费者由于生产者重发消息或者Broker服务器本身导致消息重发
        //保证消息幂等性 某项操作执行一次,或者多次达到结果异常
        //解决方案:通过传输业务数据找到业务唯一(流水号,ID)值作为判断是是否第一次执行

        //判断消息是否为重复投递
        try {
            String redisKey = "mq:" + msg;
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, null, 1, TimeUnit.MINUTES);
            if (!flag) {
                log.error("消费者重复监听消息,不做处理");
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            log.info("消费者接收到消息:{},模拟执行业务", msg);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            throw new RuntimeException(e);
            //进行消息重新入队,再次投递-参考生产者
            //todo 重新入队次数达到上限,通知运维人员;将消费失败消息发送到异常队列,提供单独消费者,将异常任务存入到异常任务表中,人工处理
        }
    }
}
